/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
/* eslint-disable jsx-a11y/tabindex-no-positive */
import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { Divider, Popover } from 'antd';
import classNames from 'classnames';
import { useInterval } from '@/hooks';
import Portal from '@/molecules/Guide/Portal';
import GuideContext from './GuideContext';
import styles from './index.module.less';

/**
 * 向上查找有出现滚动条的(视为容器组件)元素
 * @param targetEl
 * @returns {null}
 */
const closestContainerElement = targetEl => {
    let result = null;
    if (targetEl) {
        const currentEl = targetEl.parentElement;
        if (currentEl) {
            if (currentEl.scrollTop > 0) {
                result = currentEl;
            } else {
                result = closestContainerElement(currentEl);
            }
        }
    }
    return result;
};

const GuideStep = props => {
    const { children, index, title, content, placeholderStyle, placeholderOffset, ...popoverProps } = props;
    const { state, dispatch } = useContext(GuideContext);
    const { active, visible, currentStepIndex, steps, overlay, zIndex, isShowSkipBtn } = state;
    const { finishText = state.finishText, nextText = state.nextText, skipText = state.skipText } = props;
    const popoverRef = useRef(null);
    const isCurrent = steps[currentStepIndex]?.el === popoverRef.current;
    const isShow = active && visible;

    // 对目标元素做预处理
    const childrenRef = useRef(null);
    const childrenContent = useMemo(() => {
        // 判断是否是原生的html标签
        if (typeof children.type === 'string') {
            return React.cloneElement(children, { ref: childrenRef, tabIndex: 999 });
        }
        // 如果是 React 组件, 添加一个包裹层
        // 因为 React 组件没有办法获取到计算出来的样式(getBoundingClientRect)
        return (
            <div ref={childrenRef} tabIndex={999}>
                {children}
            </div>
        );
    }, [children]);
    // 通过添加一个位置和目标元素一直的 html 标签, 来覆盖目标位置, 实现高亮, 和遮罩功能
    const [childrenWrapperStyle, setChildrenWrapperStyle] = useState({});

    // 辅助聚焦元素的 html 标签, 通过来回触发 focus, 切换聚焦元素来实现重新聚焦
    // 辅助元素因为是撑满全屏的, 所以滚动条不会变(不会出现抖动情况).
    const focusActiveStepHelperRef = useRef();
    useEffect(() => {
        focusActiveStepHelperRef.current = document.querySelector('.focus-active-step-helper') || null;
    });

    // 被加载时注册
    useEffect(() => {
        const { current } = popoverRef;
        dispatch({ type: 'register', payload: { el: current, index } });
        // 卸载时取消注册
        return () => dispatch({ type: 'unRegister', payload: { el: current } });
    }, [dispatch, index]);

    // 结束引导
    const handleStop = () => {
        dispatch({ type: 'stop' });
    };

    // 下一步
    const handleNext = () => {
        dispatch({ type: 'next' });
    };

    // 弹框内容
    const popoverContent = (
        <div className={styles.popoverContainer}>
            {title && <div className={styles.popoverTitle}>{title}</div>}
            <div className={styles.popoverContent}>{content}</div>
            <div className={styles.popoverFooter} style={{ visibility: isCurrent ? 'visible' : 'hidden' }}>
                {isShowSkipBtn && currentStepIndex + 1 < steps.length && (
                    <>
                        <a className={styles.popoverBtnSkip} type="link" onClick={handleStop}>
                            {skipText}
                        </a>
                        <Divider type="vertical" />
                    </>
                )}
                <a className={styles.popoverBtnNext} type="link" onClick={handleNext}>
                    {currentStepIndex + 1 >= steps.length ? finishText : nextText}
                </a>
            </div>
        </div>
    );

    // 动态计算高亮区域的大小
    const computedPlaceholderStyle = useCallback(() => {
        if (childrenRef.current && isCurrent) {
            const rect = childrenRef.current?.getBoundingClientRect();
            const { left, top, width, height } = rect;
            const {
                left: leftOffset = 0,
                top: topOffset = 0,
                width: widthOffset = 0,
                height: heightOffset = 0
            } = placeholderOffset;

            if (focusActiveStepHelperRef.current) {
                focusActiveStepHelperRef.current.focus(); // 通过切换 tabindex 来重新聚焦当前高亮节点
            }
            childrenRef.current.focus();
            setChildrenWrapperStyle({
                left: left + leftOffset,
                top: top + topOffset,
                width: width + widthOffset,
                height: height + heightOffset
            });
        } else {
            setChildrenWrapperStyle({});
        }
    }, [isCurrent, placeholderOffset]);

    // 因为异步加载等原因, 页面元素的位置可能随时变化, 设置一个时钟来更新位置
    const [startTimer, stopTimer] = useInterval(
        () => {
            computedPlaceholderStyle();
        },
        500,
        { immediate: true, manual: true }
    );

    // 在页面聚焦目标(滚动定位)元素后再显示弹框
    const [popoverVisible, setPopoverVisible] = useState(false);
    useLayoutEffect(() => {
        if (isShow && isCurrent) {
            // 因为在火狐浏览器中, 向上定位有问题, 采取的解决方案是先把容器的滚动条置顶, 再重新定位
            if (navigator.userAgent.indexOf('Firefox') > -1) {
                const containerEl = closestContainerElement(childrenRef?.current);
                if (containerEl) {
                    containerEl.scrollTo(0, 0);
                }
            }
            startTimer();
            setPopoverVisible(true);
        } else {
            stopTimer();
            setPopoverVisible(false);
        }
        return () => {
            stopTimer();
        };
    }, [isShow, isCurrent, startTimer, stopTimer]);

    return (
        <>
            <Popover
                ref={popoverRef}
                content={popoverContent}
                overlayClassName={styles.popoverWrapper}
                overlayStyle={zIndex && { zIndex }}
                {...popoverProps}
                visible={popoverVisible}
            >
                {isShow && isCurrent && childrenWrapperStyle && (
                    <Portal>
                        <div
                            className={classNames(
                                styles.placeholder,
                                overlay && styles.placeholderShadow,
                                'guide-placeholder'
                            )}
                            style={{ zIndex: zIndex || 1001, ...childrenWrapperStyle, ...placeholderStyle }}
                        />
                    </Portal>
                )}
            </Popover>
            {childrenContent}
        </>
    );
};

GuideStep.propTypes = {
    /** 序号 */
    index: PropTypes.number,
    /** 高亮区域的样式 */
    placeholderStyle: PropTypes.object,
    /** placeholderOffset 的偏移量, 默认的偏移量可以视为内边距 */
    placeholderOffset: PropTypes.object,
    /** 结束按钮的文案 */
    finishText: PropTypes.string,
    /** 下一步按钮的文案 */
    nextText: PropTypes.string,
    /** 跳过按钮的文案 */
    skipText: PropTypes.string
};
GuideStep.defaultProps = {
    index: null,
    placeholderStyle: {},
    placeholderOffset: {
        left: -12,
        top: -12,
        width: 24,
        height: 24
    },
    finishText: undefined,
    nextText: undefined,
    skipText: undefined
};
GuideStep.displayName = 'GuideStep';

export default GuideStep;
